"""
Pydantic configuration models for HEMC validation project.
此 python 项目的配置文件
"""

import sys
from pathlib import Path
from typing import Dict, Optional

from icecream import ic
from pydantic import BaseModel, Field, confloat, conint, model_validator
from pydantic_settings import BaseSettings

from ..utils.fingerprint import genfingerprint
from ..utils.tools import exe_suffix

_EPS = 1e-8


##00000000000000000000000000000000000000000000000000000000 一些公共参数
class ProjectPaths(BaseModel):
    """CXX, python 工程目录结构."""

    # geant4 c++ 工程的根目录
    g4cxxdir: Path = Path('../G4')
    ## g4 exe 编译结果, 顶层名录
    g4build_dir: str = 'build'
    ## realease or debug 构建
    g4build_config: str = "Release"
    g4exe_name: str = 'g4test'
    g4exe_path: Optional[Path] = None

    ## 后处理涉及的目录
    postdir: Path  ## 后处理根目录
    macrosdir: Optional[Path] = None
    ## geant4 模拟的输出目录
    g4outdir: Optional[Path] = None
    logdir: Optional[Path] = None
    ## python 后处理输出目录
    resultdir: Optional[Path] = None

    @model_validator(mode='after')
    def set_default_paths(self) -> 'ProjectPaths':
        """根据 postdir 动态设置其他目录的默认值"""
        # Validate that the required directories exist
        # ic(self.g4cxxdir)
        self.g4cxxdir = self.g4cxxdir.expanduser().resolve()
        ic(self.g4cxxdir)
        if not self.g4cxxdir.exists():
            print(f"[ProjectPaths]!!!g4cxxdir does not exist: {self.g4cxxdir}")

        # g4test.exe 的完整路径
        if self.g4exe_path is None:
            self.g4exe_path = self.g4cxxdir / self.g4build_dir / self.g4build_config / self.g4exe_name

        exe_full = self.g4exe_path.with_suffix(exe_suffix())
        if not self.g4exe_path.exists() and not exe_full.exists():
            print(f"[ProjectPaths]!!!g4 exe does not exist: {self.g4exe_path}")

        self.postdir = self.postdir.expanduser().resolve()
        if self.macrosdir is None:
            self.macrosdir = self.postdir / "macros"
        if self.g4outdir is None:
            self.g4outdir = self.postdir / "g4out"
        ## log, macro 暂且放一起
        if self.logdir is None:
            self.logdir = self.postdir / "macros"
        if self.resultdir is None:
            self.resultdir = self.postdir / "result"

        return self

    def create_directories(self) -> None:
        """Create all required directories if they don't exist."""
        for dir_path in [self.macrosdir, self.g4outdir, self.logdir, self.resultdir]:
            assert dir_path is not None
            dir_path.mkdir(parents=True, exist_ok=True)


class Geant4Config(BaseModel):
    """Geant4 执行参数, 也就是 jinja2 模板的填充参数."""

    g4macro_template: Path = Field(default=Path("./g4template/g4_macro.mac"), description="Path to Jinja2 template of Geant4 macro")
    threads: int = Field(default=20, ge=1, description="Number of threads for Geant4 simulation")
    physics_list: str = Field(default="em_op4", description="Physics list to use")
    beam_on: int = Field(default=10000, ge=1, description="Number of events to simulate")

    dtct_shape: str = Field(default="cylinder", description="Shape of sensitive detector")
    dtct_size_trans: float = Field(default=400, gt=0.0, description="Transverse size of sensitive detector")
    dtct_size_long: float = Field(default=600, gt=0.0, description="Longitudinal size of sensitive detector")
    world_to_dtct: float = Field(default=8.0, ge=1.0, description="Scale factor, world / phantom, e.g. 8")

    mesh1_sizes: list[confloat(gt=0)] = Field(default_factory=lambda _: [200.0, 300.0], description="")
    mesh1_bins: list[conint(gt=0)] = Field(default_factory=lambda _: [1, 1200, 1], description="")

    mesh2_sizes: list[confloat(gt=0)] = Field(
        default_factory=lambda: [200, 300],
        description="",
    )
    mesh2_bins: list[conint(gt=0)] = Field(default_factory=lambda _: [200, 300, 1], description="")

    gun_position: float = Field(default=-320.0, description="Transverse size of sensitive detector")
    g4energy: float = Field(default=40.0, gt=0.0, description="Alpha particle energy")

    @model_validator(mode='after')
    def set_default_paths(self) -> 'ProjectPaths':
        """后检验"""

        ## jinja2 模板文件路径
        self.g4macro_template = self.g4macro_template.expanduser().resolve()

        ## sf
        if self.mesh1_sizes[0] + _EPS < self.dtct_size_trans / 2.0:
            raise ValueError("mesh1 size 0 too small, compared with phantom")
        if self.mesh1_sizes[1] + _EPS < self.dtct_size_long / 2.0:
            raise ValueError("mesh1 size 1 too small, compared with phantom")
        ##
        if self.mesh2_sizes[0] + _EPS < self.dtct_size_trans / 2.0:
            raise ValueError("mesh2 size 0 too small, compared with phantom")
        if self.mesh2_sizes[1] + _EPS < self.dtct_size_long / 2.0:
            raise ValueError("mesh2 size 1 too small, compared with phantom")

        if abs(self.gun_position) + _EPS < self.dtct_size_long / 2.0:
            raise ValueError("Gun in phantom region, maybe run")

        return self

    ### 格式化成 jinja2 模板需要的 dict; 模板处理 digits format 比较笨拙
    def to_template_dict(self, engy: float) -> dict:
        ret = self.model_dump()
        ret.update(
            {
                'mesh1_sizes': " ".join(f"{x:.2f}" for x in self.mesh1_sizes),
                'mesh1_bins': " ".join(f"{x:d}" for x in self.mesh1_bins),
                'mesh2_sizes': " ".join(f"{x:.2f}" for x in self.mesh2_sizes),
                'mesh2_bins': " ".join(f"{x:d}" for x in self.mesh2_bins),
                'g4energy': f"{engy:.2f}",
            }
        )
        return ret


class SystemFingerprint(BaseModel):
    """提取所在系统指纹, 用于配置路径"""

    ## home_dir: Optional[Path] = Path.home()
    fingerprint: str | None = None
    g4bin_dir: Optional[Path] = None
    g4data_dir: Optional[Path] = None

    # ---------- 内置查找表 ----------
    _LKUPMAP: Dict[str, Dict[str, str]] = {
        "4eac38846a626f7a001d86647223149d": {  # ymy des fedora linux
            "bin": "~/cpplibs/geant4v11.3.2/bin",
            "data": "~/cpplibs/g4dataset11.3.2",
        },
        "846f672e2a83ffa1c115512d24fd25d3": {  # ymy des windows
            "bin": "C:/cppLibs/geant4-11.3.2/bin",
            "data": "C:/cpplibs/g4dataset-11.3.2",
        },
    }

    # ---------- 统一注入逻辑 ----------
    @model_validator(mode="after")
    def get_env_path(self):
        if self.fingerprint is None:
            fp = genfingerprint()
            self.fingerprint = fp
            # 普通场景：查表补全
            if fp not in SystemFingerprint._LKUPMAP:
                raise ValueError(f"未知的 fingerprint: {fp!r}")
            self.g4bin_dir = Path(SystemFingerprint._LKUPMAP[fp]['bin'])
            self.g4data_dir = Path(SystemFingerprint._LKUPMAP[fp]['data'])
        assert self.g4bin_dir is not None
        assert self.g4data_dir is not None
        self.g4bin_dir = self.g4bin_dir.expanduser().resolve()
        self.g4data_dir = self.g4data_dir.expanduser().resolve()
        return self


##00000000000000000000000000000000000000000000000000000000 具体计算过程的配置
class EnergyScanConfig(BaseModel):
    """能量扫描设置."""

    n_points: int = Field(default=20, ge=1, description="Number of energy sampling points")
    e_min: float = Field(default=40, ge=0, description="Minimum energy in MeV")
    e_max: float = Field(default=1800, ge=0, description="Maximum energy in MeV")
    e_step: float = Field(default=100, gt=0, description="step of energy")
    log_scale: bool = Field(default=True, description="Whether to use logarithmic energy sampling")
    energy_list: list[float] = Field(default_factory=list, description='能量点列表, 优先使用, 优先级 > start-end-step 指定的值')


class PlotConfig(BaseModel):
    """py plot 画图配置."""

    figsize: tuple = Field(default=(8, 5), description="Figure size for plots")
    save_dpi: int = Field(default=200, description="DPI for saved figures")
    xlabel: str = Field(default="z slice (iZ)", description="X-axis label")
    ylabel_dict: Dict[str, str] = Field(
        default={
            "eDep": "Energy deposit  / MeV",
            "LETd": "LETd  / (keV/μm)",
            "rgProj": "Range proj  / mm",
        },
        description="Y-axis labels for different quantities",
    )


class CallCfg(BaseModel):
    ## ... 表示为必须参数
    # call: str = Field(..., description="要调用的函数名称")
    func: str = Field(..., description="要调用的函数名称")


##0000000000000000000000000000000000000000000000000000000000000000000000000000 一张总表, 塞入各种模块
class HEMCConfig(BaseSettings):
    """组合各种类定义"""

    ## 需要调用的函数信息, call info
    call: list[CallCfg] = Field(default_factory=list, description="要调用的函数列表,依次执行")

    system_fingerprint: Optional[SystemFingerprint] = None  # 用于配置 LD_PATH G4 DataSet Path
    project_paths: ProjectPaths

    ## 如果用户的配置没有这个字段，就现场 new 一个
    geant4: Geant4Config = Field(default_factory=Geant4Config)

    energyscan: EnergyScanConfig = Field(default_factory=EnergyScanConfig)
    plot: PlotConfig = Field(default_factory=PlotConfig)

    # @classmethod
    # def create(cls, *args, **kwargs):
    #     """Create HEMCConfig with default paths based on project directory."""
    #     project_paths = ProjectPaths(*args, **kwargs)
    #     return cls(project_paths=project_paths, **kwargs)

    def setup_environment(self) -> None:
        """Set up environment variables based on system fingerprint."""
        import os

        if self.system_fingerprint and self.system_fingerprint.g4bin_dir:
            current_path = os.environ.get("PATH", "")
            new_path = f"{self.system_fingerprint.g4bin_dir}{os.pathsep}{current_path}"
            os.environ["PATH"] = new_path

        if self.system_fingerprint and self.system_fingerprint.g4data_dir:
            os.environ["GEANT4_DATA_DIR"] = str(self.system_fingerprint.g4data_dir)

    ## inner 转发, 创建需要的目录
    def create_required_directories(self) -> None:
        """Create all required directories."""
        self.project_paths.create_directories()
